"""
CLU API 服务层模块。

本模块提供了与 Azure Conversational Language Understanding (CLU) 服务交互的核心业务逻辑。
包含项目管理、模型训练、部署管理、意图识别和会话分析等功能。

核心服务类：
- CLUService: 基础服务类，提供 Azure 客户端初始化
- ProjectService: 项目管理服务，支持项目列表缓存优化
- TrainingService: 模型训练服务  
- DeploymentService: 部署管理服务
- ConversationService: 会话分析服务，支持CLU项目列表和CosmosDB两种目标获取方式
- IntentService: 意图管理服务（预留）

新增功能（2025-09-19）：
- ProjectService 添加缓存机制，优化 list_projects 性能
- ConversationService 新增 analyze_conversation_from_clu 方法，直接从CLU获取项目列表

最小实现调用方式：
    from services import ConversationService
    service = ConversationService()
    result = await service.analyze_conversation_from_clu(request)

作者：ZHWA
创建时间：2025-09-19 10:43:13 CST
"""

import asyncio
import warnings
from typing import Any, Dict, List, Optional
from datetime import timezone, timedelta
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeoutError
from functools import lru_cache
import time

import httpx
from azure.ai.language.conversations import ConversationAnalysisClient
from azure.ai.language.conversations.authoring import ConversationAuthoringClient
from azure.core.credentials import AzureKeyCredential
from azure.core.exceptions import HttpResponseError, ResourceNotFoundError

from log import app_logger as logger

# 东八区时区常量
CHINA_TZ = timezone(timedelta(hours=8))

from config import settings
from exceptions import (
    AnalysisError,
    CLUServiceError,
    CosmosDBConnectionError,
    CosmosDBConfigurationError,
    CosmosDBError,
    CosmosDBQueryError,
    DeploymentError,
    IntentNotFoundError,
    InvalidConfigError,
    MissingParameterError,
    ModelNotFoundError,
    ProjectExistsError,
    ProjectNotFoundError,
    TrainingError,
)
from models import (
    BatchConversationRequest,
    BatchConversationResponse,
    CheckedApplicationsRequest,
    CheckedApplicationsResponse,
    ConversationRequest,
    CosmosDBQueryRequest,
    CosmosDBQueryResponse,
    DefaultConversationRequest,
    DeploymentConfig,
    IntentResponse,
    ProjectDeploymentPair,
    ProjectResponse,
    TrainingConfig,
    UtilsBase,
)


class CLUService:
    """
    CLU 服务基础类。
    
    提供 Azure CLU 服务的基础连接和认证功能。
    所有具体的服务类都继承自此基础类。
    """
    
    def __init__(self):
        """初始化 CLU 服务，配置 Azure 认证凭据和客户端。"""
        self.credential = AzureKeyCredential(key=settings.key)
        self.client_author = ConversationAuthoringClient(settings.endpoint, self.credential)
        self.client_analyze = ConversationAnalysisClient(settings.endpoint, self.credential)

    async def get_endpoint(self) -> str:
        """获取当前配置的 Azure CLU 服务端点。"""
        return settings.endpoint


class ProjectService(CLUService):
    """
    项目管理服务。
    
    提供 CLU 项目的创建、查询、删除、导入导出等管理功能。
    支持项目列表缓存以提高性能。
    """
    
    def __init__(self):
        """初始化项目服务，配置缓存参数。"""
        super().__init__()
        self._projects_cache = None
        self._cache_timestamp = 0
        self._cache_ttl = 300  # 缓存5分钟
    
    async def list_projects(self, include_deployment_info: bool = False, use_cache: bool = True) -> List[ProjectResponse]:
        """
        获取所有项目列表，可选择是否包含最新部署信息。
        
        Args:
            include_deployment_info: 默认为 False，以提高性能。
                                     如果为 True，则会为每个项目查询并包含最新的部署信息。
            use_cache: 是否使用缓存，默认为 True。当 include_deployment_info=True 时会使用缓存。
        """
        current_time = time.time()
        
        # 如果需要部署信息且启用缓存，检查缓存是否有效
        if include_deployment_info and use_cache and self._projects_cache is not None:
            if current_time - self._cache_timestamp < self._cache_ttl:
                logger.debug("使用缓存的项目列表（包含部署信息）")
                return self._projects_cache
        
        try:
            projects = self.client_author.list_projects()
            project_responses = []
            
            deployment_service = DeploymentService() if include_deployment_info else None
            from types import SimpleNamespace

            
            # 为每个项目获取最新部署信息
            for item in projects:
                latest_deployment_name = None
                latest_deployment_training_time = None

                if include_deployment_info and deployment_service:
                    project_name = item.get('projectName')
                    target = SimpleNamespace(projectName=project_name, deploymentName="")
                    deployment_info = await deployment_service.get_latest_deployment_info(target, include_training_time=True)
                    
                    if deployment_info["status"] == "success":
                        latest_deployment_name = deployment_info.get("deploymentName")
                        latest_deployment_training_time = deployment_info.get("latestDeploymentTrainingTime")

                project_responses.append(ProjectResponse.from_api_response(
                    item, 
                    latest_deployment_name=latest_deployment_name,
                    latest_deployment_training_time=latest_deployment_training_time
                ))
            
            # 如果需要部署信息且启用缓存，更新缓存
            if include_deployment_info and use_cache:
                self._projects_cache = project_responses
                self._cache_timestamp = current_time
                logger.debug(f"更新项目列表缓存，包含{len(project_responses)}个项目")
            
            return project_responses
        except HttpResponseError as e:
            raise CLUServiceError(f"Error listing projects: {str(e)}")
    
    def clear_projects_cache(self):
        """清除项目列表缓存。"""
        self._projects_cache = None
        self._cache_timestamp = 0
        logger.debug("项目列表缓存已清除")

    async def get_project(self, project_name: str) -> ProjectResponse:
        """获取指定项目的详细信息，包含最新部署信息。"""
        try:
            project = self.client_author.get_project(project_name=project_name)
            
            # 直接调用 DeploymentService 方法，避免多余封装
            from types import SimpleNamespace
            target = SimpleNamespace(projectName=project_name, deploymentName="")
            deployment_service = DeploymentService()
            deployment_info = await deployment_service.get_latest_deployment_info(target, include_training_time=True)
            
            latest_deployment_name = deployment_info.get("deploymentName") if deployment_info["status"] == "success" else None
            latest_deployment_training_time = deployment_info.get("latestDeploymentTrainingTime") if deployment_info["status"] == "success" else None
            
            return ProjectResponse.from_api_response(
                project, 
                latest_deployment_name=latest_deployment_name,
                latest_deployment_training_time=latest_deployment_training_time
            )
        except ResourceNotFoundError:
            raise ProjectNotFoundError(f"Project {project_name} not found")
        except HttpResponseError as e:
            raise CLUServiceError(f"Error getting project: {str(e)}")
    
    async def delete_project(self, project_name: str) -> Any:
        """删除指定项目。"""
        try:
            projects = self.client_author.begin_delete_project(project_name=project_name)
            return projects.result()
        except ResourceNotFoundError:
            raise ProjectNotFoundError(f"Project {project_name} not found")
        except HttpResponseError as e:
            raise CLUServiceError(f"Error deleting project: {str(e)}")
    
    async def import_project(self, project_name: str, project: Dict[str, Any]) -> str:
        """导入项目配置。"""
        try:
            result = self.client_author.begin_import_project(
                project_name=project_name,
                project=project,
            )
            return result.result()['jobId']
        except HttpResponseError as e:
            if "already exists" in str(e).lower():
                raise ProjectExistsError(f"Project {project_name} already exists")
            raise CLUServiceError(f"Error importing project: {str(e)}")

    async def get_import_project_job_status(self, project_name: str, job_id: str) -> Any:
        """获取项目导入任务状态。"""
        try:
            return self.client_author.get_import_project_job_status(
                project_name=project_name,
                job_id=job_id
            )
        except ResourceNotFoundError:
            raise ProjectNotFoundError(f"Project {project_name} or job {job_id} not found")
        except HttpResponseError as e:
            raise CLUServiceError(f"Error getting import job status: {str(e)}")
    
    async def export_project(self, project_name: str, asset_kind: str = "intents") -> Any:
        """
        导出项目配置。
        
        Args:
            project_name: 项目名称
            asset_kind: 导出资产类型，默认为 "intents"，"None" 返回全部资产
        """
        try:
            export_job = self.client_author.begin_export_project(
                project_name=project_name,
                string_index_type="Utf16CodeUnit",
                asset_kind=asset_kind
            )
            
            # 从响应头中提取任务 ID，避免阻塞等待导出完成
            operation_location = export_job._polling_method._initial_response.http_response.headers.get('operation-location')
            if not operation_location:
                raise CLUServiceError("Could not get job ID for export operation.")
            
            job_id = operation_location.split('/')[-1].split('?')[0]
            return {"jobId": job_id}
        except ResourceNotFoundError:
            raise ProjectNotFoundError(f"Project {project_name} not found")
        except HttpResponseError as e:
            raise CLUServiceError(f"Error exporting project: {str(e)}")

    async def get_export_project_job_status(self, project_name: str, job_id: str) -> Any:
        """获取项目导出任务状态。"""
        try:
            return self.client_author.get_export_project_job_status(
                project_name=project_name,
                job_id=job_id
            )
        except ResourceNotFoundError:
            raise ProjectNotFoundError(f"Project {project_name} or job {job_id} not found")
        except HttpResponseError as e:
            raise CLUServiceError(f"Error getting export job status: {str(e)}")

    async def get_export_result(self, result_url: str) -> Any:
        """从结果 URL 获取已完成的导出任务结果。"""
        headers = {"Ocp-Apim-Subscription-Key": self.credential.key}
        async with httpx.AsyncClient() as client:
            try:
                response = await client.get(result_url, headers=headers)
                response.raise_for_status()
                return response.json()
            except httpx.HTTPStatusError as e:
                raise CLUServiceError(f"Error getting export result: {e.response.text}") from e
            except Exception as e:
                raise CLUServiceError(f"An unexpected error occurred while fetching job result: {str(e)}") from e


class TrainingService(CLUService):
    """
    模型训练服务。
    
    提供模型训练任务的启动、监控、取消和模型删除等功能。
    """
    
    async def start_training(self, project_name: str, config: TrainingConfig) -> Dict[str, str]:
        """启动模型训练任务。"""
        try:
            self.client_author.begin_train(
                project_name=project_name,
                configuration=config.dict()
            )
            return {"message": "Training started"}
        except ResourceNotFoundError:
            raise ProjectNotFoundError(f"Project {project_name} not found")
        except HttpResponseError as e:
            raise TrainingError(f"Error starting training: {str(e)}")
    
    async def list_training_jobs(self, project_name: str) -> List[Any]:
        """获取项目的所有训练任务列表。"""
        try:
            train_lists = self.client_author.list_training_jobs(project_name=project_name)
            return list(train_lists)
        except ResourceNotFoundError:
            raise ProjectNotFoundError(f"Project {project_name} not found")
        except HttpResponseError as e:
            raise TrainingError(f"Error listing training jobs: {str(e)}")

    async def get_training_status(self, project_name: str, job_id: str) -> Any:
        """获取训练任务状态。"""
        try:
            return self.client_author.get_training_job_status(
                project_name=project_name,
                job_id=job_id
            )
        except ResourceNotFoundError:
            raise ProjectNotFoundError(f"Project {project_name} or job {job_id} not found")
        except HttpResponseError as e:
            raise TrainingError(f"Error getting training status: {str(e)}")

    async def cancel_training(self, project_name: str, job_id: str) -> Dict[str, str]:
        """取消训练任务。"""
        try:
            self.client_author.begin_cancel_training_job(
                project_name=project_name,
                job_id=job_id
            )
            return {"message": "Training canceled"}
        except ResourceNotFoundError:
            raise ProjectNotFoundError(f"Project {project_name} or job {job_id} not found")
        except HttpResponseError as e:
            raise TrainingError(f"Error canceling training: {str(e)}")
    
    async def delete_model(self, project_name: str, model_label: str) -> Dict[str, str]:
        """删除已训练的模型。"""
        try:
            result = self.client_author.delete_trained_model(
                project_name=project_name,
                trained_model_label=model_label
            )
            return {"message": "model deleted" if result is None else "delete failed"}
        except ResourceNotFoundError:
            raise ModelNotFoundError(f"Model {model_label} not found in project {project_name}")
        except HttpResponseError as e:
            raise TrainingError(f"Error deleting model: {str(e)}")


class DeploymentService(CLUService):
    """
    部署管理服务。
    
    提供模型部署、部署状态查询和最新部署获取等功能。
    支持动态获取项目中训练时间最新的部署。
    """
    
    async def get_latest_deployment_info(self, target, include_training_time: bool = False) -> Dict[str, Any]:
        """
        获取目标项目的最新部署信息，构建目标字典。
        
        统一方法，避免重复调用 list_deployments，提高性能。
        
        Args:
            target: 目标项目配置（包含 projectName 和 deploymentName 属性）
            include_training_time: 是否包含训练时间信息
            
        Returns:
            包含项目名称、部署名称、状态和可选训练时间的字典
        """
        try:
            # 一次性获取部署列表，避免重复调用
            deployments = await self.list_deployments(target.projectName)
            
            def get_training_time(deployment_dict):
                """安全地从部署字典中获取训练时间，兼容多种命名格式。"""
                if isinstance(deployment_dict, dict):
                    return deployment_dict.get('lastTrainedDateTime') or deployment_dict.get('last_trained_date_time')
                else:
                    return getattr(deployment_dict, 'last_trained_date_time', 
                                 getattr(deployment_dict, 'lastTrainedDateTime', None))

            valid_deployments = [d for d in deployments if get_training_time(d)]
            
            if not valid_deployments:
                raise DeploymentError(f"项目 {target.projectName} 中未找到任何带有训练日期的有效部署。")

            latest_deployment = max(valid_deployments, key=get_training_time)
            deployment_name = self._get_deployment_name(latest_deployment)
            
            if not deployment_name:
                raise DeploymentError(f"在项目 {target.projectName} 的最新部署中未能找到部署名称。")
            
            result = {
                "projectName": target.projectName,
                "deploymentName": deployment_name,
                "status": "success"
            }
            
            # 如果需要训练时间，直接从已找到的最新部署中获取
            if include_training_time:
                training_time = self._get_training_time(latest_deployment)
                latest_deployment_training_time = self._format_training_time(training_time) if training_time else None
                result["latestDeploymentTrainingTime"] = latest_deployment_training_time
            
            return result
            
        except Exception as e:
            # 如果获取最新部署失败，使用配置中的原始部署名称
            error_result = {
                "projectName": target.projectName,
                "deploymentName": target.deploymentName,
                "status": "error",
                "error": str(e)
            }
            
            if include_training_time:
                error_result["latestDeploymentTrainingTime"] = None
                
            return error_result

    async def get_latest_deployment_name(self, project_name: str) -> str:
        """
        获取项目中训练时间最新的部署名称。
        
        为了保持向后兼容性而保留的简化接口。
        内部调用 get_latest_deployment_info 方法。
        
        Args:
            project_name: 项目名称
            
        Returns:
            最新部署的名称
            
        Raises:
            DeploymentError: 当项目中没有找到有效部署时
        """
        from types import SimpleNamespace
        target = SimpleNamespace(projectName=project_name, deploymentName="")
        result = await self.get_latest_deployment_info(target, include_training_time=False)
        
        if result["status"] == "success":
            return result["deploymentName"]
        else:
            raise DeploymentError(f"项目 {project_name} 中未找到任何带有训练日期的有效部署。")

    def _get_deployment_name(self, deployment) -> Optional[str]:
        """从部署对象中获取部署名称，兼容多种格式。"""
        if isinstance(deployment, dict):
            return deployment.get('deploymentName') or deployment.get('deployment_name')
        else:
            return getattr(deployment, 'deployment_name', 
                          getattr(deployment, 'deploymentName', None))

    def _get_training_time(self, deployment):
        """从部署对象中获取训练时间，兼容多种格式。"""
        if isinstance(deployment, dict):
            return deployment.get('lastTrainedDateTime') or deployment.get('last_trained_date_time')
        else:
            return getattr(deployment, 'last_trained_date_time', 
                          getattr(deployment, 'lastTrainedDateTime', None))

    def _format_training_time(self, training_time) -> str:
        """格式化训练时间为 YYYY-MM-DD HH:MM:SS 格式。"""
        if isinstance(training_time, str):
            try:
                from datetime import datetime
                # 使用东八区时区常量
                dt = datetime.fromisoformat(training_time.replace('Z', '+00:00'))
                # 转换为东八区时间
                dt_china = dt.astimezone(CHINA_TZ)
                return dt_china.strftime('%Y-%m-%d %H:%M:%S')
            except:
                return training_time
        else:
            # 如果是 datetime 对象，直接格式化
            return training_time.strftime('%Y-%m-%d %H:%M:%S') if hasattr(training_time, 'strftime') else str(training_time)
    
    async def list_deployments(self, project_name: str, top: Optional[int] = None, skip: Optional[int] = None) -> List[Any]:
        """
        获取项目的所有部署列表。
        
        使用线程执行器来避免阻塞事件循环，确保在并发环境下的稳定性。
        """
        def _list_sync():
            """同步包装器，在执行器中运行。"""
            # SDK 的 list_deployments 返回迭代器，会进行阻塞 I/O 分页调用
            # 必须在执行器线程中完全消费
            return list(self.client_author.list_deployments(
                project_name=project_name,
                top=top,
                skip=skip
            ))

        try:
            loop = asyncio.get_event_loop()
            return await loop.run_in_executor(None, _list_sync)
        except ResourceNotFoundError:
            raise ProjectNotFoundError(f"Project {project_name} not found")
        except HttpResponseError as e:
            raise DeploymentError(f"Error listing deployments: {str(e)}")

    async def deploy_model(self, project_name: str, deployment_name: str, config: DeploymentConfig) -> Any:
        """部署模型到指定的部署名称。"""
        try:
            deployment = self.client_author.begin_deploy(
                project_name=project_name,
                deployment_name=deployment_name,
                deployment=config.dict()
            )
            return deployment.result()
        except ResourceNotFoundError:
            raise ProjectNotFoundError(f"Project {project_name} or model not found")
        except HttpResponseError as e:
            if "model not found" in str(e).lower():
                raise ModelNotFoundError(f"Model {config.trainedModelLabel} not found")
            raise DeploymentError(f"Error deploying model: {str(e)}")
    
    async def get_deployment_status(self, project_name: str, deployment_name: str) -> Any:
        """获取部署状态。"""
        try:
            return self.client_author.get_deployment(
                project_name=project_name,
                deployment_name=deployment_name
            )
        except ResourceNotFoundError:
            raise ProjectNotFoundError(f"Project {project_name} or deployment {deployment_name} not found")
        except HttpResponseError as e:
            raise DeploymentError(f"Error getting deployment status: {str(e)}")


class IntentService(CLUService):
    """
    意图管理服务。
    
    当前作为基础类存在，实际的意图操作通过 sync_list_intents 端点实现。
    预留用于未来的意图管理功能扩展。
    """
    pass


class UtilsService(CLUService):
    """
    工具服务类。
    
    提供各种工具功能，包括CosmosDB查询等。
    采用参数化查询防止注入攻击，确保数据安全。
    """
    
    def __init__(self):
        """初始化工具服务，配置CosmosDB连接。"""
        super().__init__()
        self._cosmos_client = None
        self._cosmos_database = None
        self._cosmos_container = None
        self._executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix="cosmos-db")
        # 连接和查询超时设置（单位：秒）；Cosmos Python SDK 参数期望毫秒，这里同时保存毫秒值
        self._connection_timeout_seconds = settings.cosmos_connection_timeout
        self._query_timeout_seconds = settings.cosmos_query_timeout
        self._connection_timeout_ms = int(self._connection_timeout_seconds * 1000)
        self._query_timeout_ms = int(self._query_timeout_seconds * 1000)
    
    async def _get_cosmos_container(self):
        """获取CosmosDB容器实例，使用懒加载模式，带超时控制。"""
        if self._cosmos_container is None:
            def _create_cosmos_connection():
                """在线程池中执行的同步连接函数。"""
                try:
                    from azure.cosmos import CosmosClient
                    
                    if not all([settings.cosmos_account_endpoint, settings.cosmos_account_key]):
                        raise CosmosDBConfigurationError("CosmosDB连接配置不完整")
                    
                    # 创建客户端连接 - 正确传入毫秒级超时，避免把秒误当成毫秒
                    client = CosmosClient(
                        settings.cosmos_account_endpoint,
                        settings.cosmos_account_key,
                        connection_timeout=self._connection_timeout_ms,
                        request_timeout=self._connection_timeout_ms
                    )
                    
                    # 获取数据库和容器
                    database = client.get_database_client(settings.cosmos_database_name)
                    container = database.get_container_client(settings.cosmos_container_name)
                    
                    # 跳过连接测试以避免超时，直接返回容器实例
                    logger.info(f"CosmosDB客户端创建成功：{settings.cosmos_database_name}/{settings.cosmos_container_name}")
                    
                    return client, database, container
                    
                except ImportError:
                    raise CosmosDBConfigurationError("azure-cosmos包未安装，请运行：pip install azure-cosmos")
                except Exception as e:
                    logger.error(f"CosmosDB连接失败：{str(e)}")
                    raise CosmosDBConnectionError(f"CosmosDB连接失败：{str(e)}")
            
            try:
                loop = asyncio.get_event_loop()
                future = loop.run_in_executor(self._executor, _create_cosmos_connection)
                
                client, database, container = await asyncio.wait_for(
                    future, 
                    timeout=self._connection_timeout_seconds
                )
                
                self._cosmos_client = client
                self._cosmos_database = database
                self._cosmos_container = container
                
                logger.info(f"CosmosDB异步连接成功：{settings.cosmos_database_name}/{settings.cosmos_container_name}")
                
            except asyncio.TimeoutError:
                logger.error(f"CosmosDB连接超时（{self._connection_timeout_seconds}秒）")
                raise CosmosDBConnectionError(f"CosmosDB连接超时（{self._connection_timeout_seconds}秒）")
            except FutureTimeoutError:
                logger.error(f"CosmosDB连接线程池超时（{self._connection_timeout_seconds}秒）")
                raise CosmosDBConnectionError(f"CosmosDB连接线程池超时（{self._connection_timeout_seconds}秒）")
            except Exception as e:
                logger.error(f"CosmosDB异步连接失败：{str(e)}")
                raise CosmosDBConnectionError(f"CosmosDB异步连接失败：{str(e)}")
        
        return self._cosmos_container
    
    async def query_cosmos_setting(self, request: CosmosDBQueryRequest) -> CosmosDBQueryResponse:
        """
        从CosmosDB查询配置值。
        
        使用参数化查询防止SQL注入攻击。
        查询参数均为固定值，不接受外部输入。
        
        Args:
            request: 查询请求，包含目标键名
            
        Returns:
            查询响应，包含键值和查询状态
            
        Raises:
            CosmosDBQueryError: 查询执行失败
            CosmosDBConnectionError: 连接失败
        """
        from datetime import datetime
        
        query_start_time = datetime.now(CHINA_TZ)
        
        try:
            container = await self._get_cosmos_container()
            
            # 使用参数化查询，防止注入攻击
            # 根据CosmosDB实际数据结构：通过key字段查询，而不是id字段
            query = "SELECT * FROM c WHERE c.key = @target_key"
            parameters = [{"name": "@target_key", "value": request.target_key}]
            
            # logger.debug(f"执行CosmosDB查询：key={request.target_key}")
            
            # 在线程池中异步执行查询操作，并使用 asyncio 超时保护，避免阻塞事件循环
            def _execute_query():
                return list(container.query_items(
                    query=query,
                    parameters=parameters,
                    max_item_count=1,
                    enable_cross_partition_query=True
                ))

            loop = asyncio.get_event_loop()
            future = loop.run_in_executor(self._executor, _execute_query)
            try:
                items = await asyncio.wait_for(future, timeout=self._query_timeout_seconds)
            except asyncio.TimeoutError:
                logger.error(f"CosmosDB查询超时（{self._query_timeout_seconds}秒）：key={request.target_key}")
                raise CosmosDBQueryError(f"查询超时（{self._query_timeout_seconds}秒）")
            
            # 处理查询结果
            if items:
                item = items[0]  # 取第一个匹配项
                value = item.get('value')
                found = True
                # logger.debug(f"CosmosDB查询成功：key={request.target_key}, value={value}")
            else:
                value = None
                found = False
                logger.warning(f"CosmosDB查询无结果：key={request.target_key}")
            
            return CosmosDBQueryResponse(
                key=request.target_key,
                value=value,
                found=found,
                query_time=query_start_time
            )
            
        except Exception as e:
            logger.error(f"CosmosDB查询失败：{str(e)}")
            raise CosmosDBQueryError(f"查询执行失败：{str(e)}")

    async def get_threshold_score(self) -> Optional[float]:
        """
        获取LUIS阈值分数配置。
        
        这是一个业务层方法，直接返回threshold score的数值，
        避免暴露底层CosmosDB查询接口。
        
        Returns:
            float: LUIS阈值分数，如果未找到则返回None
            
        Raises:
            CosmosDBQueryError: 查询执行失败
            CosmosDBConnectionError: 连接失败
        """
        try:
            request = CosmosDBQueryRequest(target_key="System.LUIS.ThresholdScore")
            response = await self.query_cosmos_setting(request)
            
            if response.found and response.value is not None:
                # 尝试转换为float类型
                try:
                    threshold_score = float(response.value)
                    # logger.debug(f"成功获取CLU阈值分数：{threshold_score}")
                    return threshold_score
                except (ValueError, TypeError) as e:
                    logger.error(f"LUIS阈值分数格式错误，无法转换为float：{response.value}, 错误：{str(e)}")
                    return None
            else:
                logger.warning("未找到LUIS阈值分数配置")
                return None
                
        except Exception as e:
            logger.error(f"获取LUIS阈值分数失败：{str(e)}")
            raise

    async def get_checked_applications(self, request: CheckedApplicationsRequest) -> CheckedApplicationsResponse:
        """
        获取已检查的LUIS应用程序列表。
        
        从CosmosDB查询System.LUIS.CheckedApplications配置，
        支持根据checked字段进行筛选。
        
        Args:
            request: 查询请求，包含checked筛选条件
            
        Returns:
            CheckedApplicationsResponse: 包含应用程序名称列表和统计信息
            
        Raises:
            CosmosDBQueryError: 查询执行失败
            CosmosDBConnectionError: 连接失败
        """
        from datetime import datetime
        
        query_start_time = datetime.now(CHINA_TZ)
        
        try:
            # 查询System.LUIS.CheckedApplications配置
            cosmos_request = CosmosDBQueryRequest(target_key="System.LUIS.CheckedApplications")
            response = await self.query_cosmos_setting(cosmos_request)
            
            if not response.found or response.value is None:
                logger.warning("未找到System.LUIS.CheckedApplications配置")
                return CheckedApplicationsResponse(
                    application_names=[],
                    total_count=0,
                    filtered_count=0,
                    checked_filter=request.checked,
                    query_time=query_start_time
                )
            
            applications_data = response.value
            if not isinstance(applications_data, list):
                logger.error(f"System.LUIS.CheckedApplications数据格式错误，期望list，实际：{type(applications_data)}")
                raise CosmosDBQueryError("应用程序数据格式错误")
            
            total_count = len(applications_data)
            
            filtered_applications = []
            if request.checked is None:
                filtered_applications = applications_data
                logger.info(f"不进行checked筛选，返回所有{total_count}个应用程序")
            else:
                for app in applications_data:
                    if isinstance(app, dict) and app.get('checked') == request.checked:
                        filtered_applications.append(app)
                logger.info(f"根据checked={request.checked}筛选，{total_count}个project中有{len(filtered_applications)}个project激活")
            
            application_names = []
            for app in filtered_applications:
                if isinstance(app, dict) and 'applicationName' in app:
                    app_name = app['applicationName']
                    if app_name:  
                        application_names.append(app_name)
            
            filtered_count = len(application_names)
            
            logger.debug(f"成功获取已检查应用程序列表：总数={total_count}, 筛选后={filtered_count}, checked筛选条件={request.checked}")
            
            return CheckedApplicationsResponse(
                application_names=application_names,
                total_count=total_count,
                filtered_count=filtered_count,
                checked_filter=request.checked,
                query_time=query_start_time
            )
            
        except Exception as e:
            logger.error(f"获取已检查应用程序列表失败：{str(e)}")
            raise CosmosDBQueryError(f"查询已检查应用程序失败：{str(e)}")

    async def close(self):
        """关闭CosmosDB连接和线程池资源。"""
        try:
            if self._executor:
                self._executor.shutdown(wait=False)
                logger.info("CosmosDB线程池已关闭")
            
            self._cosmos_container = None
            self._cosmos_database = None
            self._cosmos_client = None
            
        except Exception as e:
            logger.error(f"关闭CosmosDB资源时出错：{str(e)}")
    
    def __del__(self):
        """析构函数，确保资源被清理。"""
        try:
            if hasattr(self, '_executor') and self._executor:
                self._executor.shutdown(wait=False)
        except Exception:
            pass  # 忽略析构函数中的错误


class LogsService:
    """
    日志文件管理服务。
    
    提供日志文件列表查询和内容读取功能，支持分页查看。
    遵循安全原则，仅允许访问指定日志目录内的文件。
    """
    
    def __init__(self):
        """初始化日志服务。"""
        from log import LogConfig
        from pathlib import Path
        
        self.log_dir = Path(LogConfig.LOG_DIR)
        
    async def list_log_files(self) -> Dict[str, Any]:
        """
        列出所有可用的日志文件。
        
        Returns:
            包含日志文件列表和统计信息的字典
            
        Raises:
            CLUServiceError: 日志目录不存在时
        """
        import os
        from datetime import datetime
        
        if not self.log_dir.exists():
            raise CLUServiceError("日志目录不存在", status_code=404)
        
        files = []
        for file_path in self.log_dir.iterdir():
            if file_path.is_file():
                stat = file_path.stat()
                files.append({
                    "name": file_path.name,
                    "size": stat.st_size,
                    "size_human": self._format_file_size(stat.st_size),
                    "modified_time": datetime.fromtimestamp(stat.st_mtime, CHINA_TZ).isoformat(),
                    "is_compressed": file_path.suffix == '.zip'
                })
        
        # 按修改时间倒序排列（最新的在前）
        files.sort(key=lambda x: x["modified_time"], reverse=True)
        
        return {
            "log_directory": str(self.log_dir.absolute()),
            "total_files": len(files),
            "files": files
        }
    
    async def get_log_file_content(
        self, 
        filename: str, 
        lines: Optional[int] = None, 
        offset: Optional[int] = None
    ) -> Dict[str, Any]:
        """
        获取日志文件内容，支持分页。
        
        Args:
            filename: 日志文件名
            lines: 限制返回的行数（从offset开始）
            offset: 跳过的行数（用于分页）
            
        Returns:
            包含日志内容和元数据的字典
            
        Raises:
            CLUServiceError: 文件不存在或读取失败时
        """
        import zipfile
        from datetime import datetime
        
        file_path = self.log_dir / filename
        
        # 安全检查：确保文件在日志目录内
        if not self._is_safe_path(self.log_dir, file_path):
            raise CLUServiceError("无效的文件路径", status_code=400)
        
        if not file_path.exists():
            raise CLUServiceError(f"日志文件 '{filename}' 不存在", status_code=404)
        
        try:
            # 处理压缩文件
            if filename.endswith('.zip'):
                content_lines = self._read_compressed_log(file_path)
            else:
                content_lines = self._read_regular_log(file_path)
            
            total_lines = len(content_lines)
            
            # 应用分页逻辑
            start_idx = offset or 0
            end_idx = start_idx + (lines or total_lines)
            filtered_lines = content_lines[start_idx:end_idx]
            
            # 获取文件统计信息
            stat = file_path.stat()
            
            return {
                "filename": filename,
                "total_lines": total_lines,
                "returned_lines": len(filtered_lines),
                "start_line": start_idx + 1,  # 1-based indexing for display
                "file_size": stat.st_size,
                "file_size_human": self._format_file_size(stat.st_size),
                "modified_time": datetime.fromtimestamp(stat.st_mtime, CHINA_TZ).isoformat(),
                "is_compressed": filename.endswith('.zip'),
                "content": filtered_lines
            }
            
        except Exception as e:
            logger.error(f"读取日志文件失败: {filename}, 错误: {str(e)}")
            raise CLUServiceError(f"读取日志文件失败: {str(e)}", status_code=500)
    
    def _is_safe_path(self, base_dir, target_path) -> bool:
        """检查目标路径是否在基础目录内（防止路径遍历攻击）。"""
        try:
            target_path.resolve().relative_to(base_dir.resolve())
            return True
        except ValueError:
            return False
    
    def _format_file_size(self, size_bytes: int) -> str:
        """格式化文件大小为人类可读格式。"""
        if size_bytes == 0:
            return "0 B"
        
        size_names = ["B", "KB", "MB", "GB", "TB"]
        import math
        i = int(math.floor(math.log(size_bytes, 1024)))
        p = math.pow(1024, i)
        s = round(size_bytes / p, 2)
        return f"{s} {size_names[i]}"
    
    def _read_regular_log(self, file_path) -> List[str]:
        """读取常规日志文件。"""
        with open(file_path, 'r', encoding='utf-8') as f:
            return f.readlines()
    
    def _read_compressed_log(self, file_path) -> List[str]:
        """读取压缩的日志文件。"""
        import zipfile
        
        with zipfile.ZipFile(file_path, 'r') as zip_file:
            # 假设压缩包中只有一个日志文件
            names = zip_file.namelist()
            if not names:
                return []
            
            # 读取第一个文件（通常是唯一的日志文件）
            with zip_file.open(names[0]) as f:
                content = f.read().decode('utf-8')
                return content.splitlines(keepends=True)


class ConversationService(CLUService):
    """
    会话分析服务。
    
    提供单个会话分析和批量会话分析功能，支持多项目并发分析和最佳意图选择。
    集成CosmosDB配置查询，自动获取置信度阈值和默认目标项目。
    """
    
    def __init__(self, utils_service: Optional['UtilsService'] = None):
        """初始化会话分析服务，可选择传入UtilsService实例用于获取配置。"""
        super().__init__()
        self._utils_service = utils_service
    
    def _log_conversation_result(
        self, 
        message: str, 
        hint_intent: Optional[str] = None,
        source_project: Optional[str] = None,
        confidence_score: Optional[float] = None,
        entities: Optional[List[Dict[str, Any]]] = None,
        analysis_type: str = "single"
    ) -> None:
        """
        记录会话分析结果的统一日志方法。
        
        Args:
            message: 用户输入的消息
            hint_intent: 命中的意图名称
            source_project: 意图来源的项目名称
            confidence_score: 置信度分数
            entities: 实体信息列表
            analysis_type: 分析类型（single/batch）
        """
        # 构建日志消息
        log_parts = [
            f"CONVERSATION_ANALYSIS | Type: {analysis_type}",
            f"Message: '{message[:100]}{'...' if len(message) > 100 else ''}'",  # 限制消息长度避免日志过长
            f"HintIntent: {hint_intent or 'None'}",
            f"SourceProject: {source_project or 'None'}",
            f"ConfidenceScore: {confidence_score if confidence_score is not None else 'None'}"
        ]
        
        # 处理entities信息
        if entities:
            entities_summary = f"[{len(entities)} entities]"
            # 如果entities不为空，记录实体类型概要
            entity_types = set()
            for entity in entities:
                if isinstance(entity, dict) and 'category' in entity:
                    entity_types.add(entity['category'])
            if entity_types:
                entities_summary += f" Types: {', '.join(sorted(entity_types))}"
        else:
            entities_summary = "None"
        
        log_parts.append(f"Entities: {entities_summary}")
        
        # 记录INFO级别日志
        log_message = " | ".join(log_parts)
        logger.info(log_message)
    
    async def analyze_conversation(self, request: ConversationRequest) -> Any:
        """
        分析单个会话的意图。
        
        使用线程执行器来避免阻塞事件循环，支持意图数量限制。
        """
        self._validate_request(request)
        
        try:
            task = self._build_conversation_task(request)
            
            # 使用线程池执行同步的 Azure SDK 调用，避免阻塞事件循环
            loop = asyncio.get_event_loop()
            result = await loop.run_in_executor(
                None, 
                self.client_analyze.analyze_conversation,
                task
            )
            
            # 获取置信度阈值并限制 Intent 数量
            confidence_threshold = await self._get_confidence_threshold()
            if (request.top_intents and request.top_intents > 0) or confidence_threshold is not None:
                result = self._limit_intents(result, request.top_intents, confidence_threshold)
            
            return result
        except ResourceNotFoundError:
            raise ProjectNotFoundError(f"Project {request.projectName} or deployment {request.deploymentName} not found")
        except HttpResponseError as e:
            raise AnalysisError(f"Error analyzing conversation: {str(e)}")
    
    async def analyze_conversation_batch(self, request: BatchConversationRequest) -> BatchConversationResponse:
        """
        批量分析会话，支持多个项目/部署对并发处理。
        
        .. deprecated:: 
            该方法已弃用，请使用 analyze_conversation_with_cosmos_targets() 方法替代。
            该方法会并发地对所有目标项目进行意图分析，
            然后从所有结果中选择置信度最高的意图作为最终结果。
        
        Args:
            request: 批量会话分析请求对象
            
        Returns:
            BatchConversationResponse: 批量分析结果
            
        Raises:
            DeprecationWarning: 方法已弃用警告
        """
        warnings.warn(
            "analyze_conversation_batch() 方法已弃用，请使用 analyze_conversation_with_cosmos_targets() 方法替代",
            DeprecationWarning,
            stacklevel=2
        )
        
        # 验证 targets 列表不为空
        if not request.targets:
            raise MissingParameterError("targets list cannot be empty.")
        
        success_count = 0
        error_count = 0
        
        async def analyze_single_target(target: ProjectDeploymentPair):
            """分析单个目标的辅助函数。"""
            try:
                # 创建单个会话请求
                conv_request = ConversationRequest(
                    message=request.message,
                    projectName=target.projectName,
                    deploymentName=target.deploymentName,
                    language=request.language,
                    id=request.id,
                    participantId=request.participantId,
                    verbose=request.verbose,
                    top_intents=request.top_intents
                )
                
                # 调用单个分析方法
                result = await self.analyze_conversation(conv_request)
                
                return {
                    "projectName": target.projectName,
                    "deploymentName": target.deploymentName,
                    "status": "success",
                    "result": result
                }
                
            except Exception as e:
                logger.error(f"Error analyzing conversation for {target.projectName}/{target.deploymentName}: {str(e)}")
                return {
                    "projectName": target.projectName,
                    "deploymentName": target.deploymentName,
                    "status": "error",
                    "error": str(e)
                }
        
        # 并发执行所有分析任务
        tasks = [analyze_single_target(target) for target in request.targets]
        results = await asyncio.gather(*tasks, return_exceptions=False)
        
        # 统计成功和失败数量
        for result in results:
            if result["status"] == "success":
                success_count += 1
            else:
                error_count += 1
        
        # 获取置信度阈值并选择最佳意图
        confidence_threshold = await self._get_confidence_threshold()
        best_intent_info = self._select_best_intent(results, confidence_threshold)
        
        # 记录批量会话分析结果日志
        try:
            self._log_conversation_result(
                message=request.message,
                hint_intent=best_intent_info.get("intent"),
                source_project=best_intent_info.get("project"),
                confidence_score=best_intent_info.get("confidence"),
                entities=best_intent_info.get("entities"),
                analysis_type="batch"
            )
        except Exception as log_error:
            # 日志记录失败不应影响正常业务流程
            logger.warning(f"记录批量会话分析日志失败: {str(log_error)}")
        
        return BatchConversationResponse(
            message=request.message,
            results=results,
            success_count=success_count,
            error_count=error_count,
            hintIntent=best_intent_info.get("intent"),
            sourceProject=best_intent_info.get("project"),
            sourceDeployment=best_intent_info.get("deployment"),
            confidenceScore=best_intent_info.get("confidence"),
            entities=best_intent_info.get("entities")
        )

    async def analyze_conversation_with_cosmos_targets(self, request: 'DefaultConversationRequest') -> BatchConversationResponse:
        """
        使用CosmosDB中的已检查应用程序进行批量会话分析。
        
        该方法封装了从CosmosDB获取默认目标、获取最新部署信息、
        构建批量请求并执行分析的完整流程。
        
        Args:
            request: 默认会话分析请求（不包含targets字段）
            
        Returns:
            BatchConversationResponse: 批量分析结果
            
        Raises:
            InvalidConfigError: 未找到已检查的应用程序
            DeploymentError: 未找到有效的部署
            CosmosDBError: CosmosDB相关错误
        """
        
        utils_service = self._utils_service
        if utils_service is None:
            utils_service = UtilsService()
            should_close_utils = True
        else:
            should_close_utils = False
        
        try:
            checked_apps_request = CheckedApplicationsRequest(checked=True)
            checked_apps_response = await utils_service.get_checked_applications(checked_apps_request)
            
            if not checked_apps_response.application_names:
                raise InvalidConfigError("No checked applications found in CosmosDB. Please configure checked applications.")
            
            logger.info(f"从CosmosDB获取到{checked_apps_response.filtered_count}个已检查的应用程序")
            
            project_targets = [
                ProjectDeploymentPair(projectName=app_name, deploymentName="")
                for app_name in checked_apps_response.application_names
            ]
            
            deployment_service = DeploymentService()
            tasks = [
                deployment_service.get_latest_deployment_info(target, include_training_time=False) 
                for target in project_targets
            ]
            deployment_results = await asyncio.gather(*tasks, return_exceptions=True)
            
            valid_targets = []
            for i, result in enumerate(deployment_results):
                if isinstance(result, Exception):
                    logger.warning(f"获取项目 {project_targets[i].projectName} 的部署信息失败: {str(result)}")
                    continue
                
                if result.get("status") == "success":
                    valid_targets.append(ProjectDeploymentPair(
                        projectName=result["projectName"],
                        deploymentName=result["deploymentName"]
                    ))
                    logger.debug(f"项目 {result['projectName']} 的最新部署: {result['deploymentName']}")
                else:
                    logger.warning(f"项目 {project_targets[i].projectName} 没有有效部署: {result.get('error', 'Unknown error')}")
            
            if not valid_targets:
                raise DeploymentError("No valid deployments found for any checked applications.")
            
            logger.info(f"找到{len(valid_targets)}个有效的部署目标")
            
            batch_request = BatchConversationRequest(
                message=request.message,
                targets=valid_targets,
                language=request.language,
                id=request.id,
                participantId=request.participantId,
                verbose=request.verbose,
                top_intents=request.top_intents
            )
            
            batch_result = await self.analyze_conversation_batch(batch_request)
            
            # 记录从CosmosDB目标分析的特殊日志
            try:
                self._log_conversation_result(
                    message=request.message,
                    hint_intent=batch_result.hintIntent,
                    source_project=batch_result.sourceProject,
                    confidence_score=batch_result.confidenceScore,
                    entities=batch_result.entities,
                    analysis_type="cosmos_targets"
                )
            except Exception as log_error:
                # 日志记录失败不应影响正常业务流程
                logger.warning(f"记录CosmosDB目标分析日志失败: {str(log_error)}")
            
            return batch_result
            
        finally:
            if should_close_utils and utils_service:
                await utils_service.close()

    async def analyze_conversation_from_clu(self, request: 'DefaultConversationRequest') -> BatchConversationResponse:
        """
        使用CLU项目列表进行批量会话分析。
        
        该方法先通过list_projects(include_deployment_info=True)获取所有项目及其最新部署信息，
        然后向这些ProjectDeploymentPair发送意图分析请求。
        使用缓存机制优化list_projects的性能。
        
        Args:
            request: 默认会话分析请求（不包含targets字段）
            
        Returns:
            BatchConversationResponse: 批量分析结果
            
        Raises:
            InvalidConfigError: 未找到任何项目
            DeploymentError: 未找到有效的部署
            CLUServiceError: CLU服务相关错误
        """
        
        project_service = ProjectService()
        
        try:
            # 使用缓存获取所有项目及其最新部署信息
            logger.info("开始从CLU获取项目列表（包含部署信息）")
            projects = await project_service.list_projects(include_deployment_info=True, use_cache=True)
            
            if not projects:
                raise InvalidConfigError("No projects found in CLU. Please create projects first.")
            
            logger.info(f"从CLU获取到{len(projects)}个项目")
            
            # 构建有效的项目部署对列表
            valid_targets = []
            for project in projects:
                if project.latestDeploymentName:
                    valid_targets.append(ProjectDeploymentPair(
                        projectName=project.name,
                        deploymentName=project.latestDeploymentName
                    ))
                    logger.debug(f"项目 {project.name} 的最新部署: {project.latestDeploymentName}")
                else:
                    logger.warning(f"项目 {project.name} 没有有效部署，跳过")
            
            if not valid_targets:
                raise DeploymentError("No valid deployments found for any projects.")
            
            logger.info(f"找到{len(valid_targets)}个有效的部署目标")
            
            # 构建批量请求
            batch_request = BatchConversationRequest(
                message=request.message,
                targets=valid_targets,
                language=request.language,
                id=request.id,
                participantId=request.participantId,
                verbose=request.verbose,
                top_intents=request.top_intents
            )
            
            # 执行批量分析
            batch_result = await self.analyze_conversation_batch(batch_request)
            
            # 记录从CLU项目列表分析的特殊日志
            try:
                self._log_conversation_result(
                    message=request.message,
                    hint_intent=batch_result.hintIntent,
                    source_project=batch_result.sourceProject,
                    confidence_score=batch_result.confidenceScore,
                    entities=batch_result.entities,
                    analysis_type="clu_projects"
                )
            except Exception as log_error:
                # 日志记录失败不应影响正常业务流程
                logger.warning(f"记录CLU项目列表分析日志失败: {str(log_error)}")
            
            return batch_result
            
        except Exception as e:
            logger.error(f"从CLU项目列表进行会话分析失败: {str(e)}")
            raise
    
    def _validate_request(self, request: ConversationRequest) -> None:
        """验证会话分析请求参数。"""
        if not request.projectName or not request.projectName.strip():
            raise MissingParameterError("projectName is required and cannot be empty.")
        if not request.deploymentName or not request.deploymentName.strip():
            raise MissingParameterError("deploymentName is required and cannot be empty.")
    
    def _build_conversation_task(self, request: ConversationRequest) -> Dict[str, Any]:
        """构建会话分析任务的请求体。"""
        return {
            "kind": "Conversation",
            "analysisInput": {
                "conversationItem": {
                    "id": request.id,
                    "participantId": request.participantId,
                    "text": request.message,
                    "language": request.language,
                }
            },
            "parameters": {
                "projectName": request.projectName,
                "deploymentName": request.deploymentName,
                "stringIndexType": "Utf16CodeUnit",
                "verbose": request.verbose
            }
        }
    
    async def _get_confidence_threshold(self) -> Optional[float]:
        """
        从CosmosDB获取置信度阈值配置。
        
        Returns:
            float: 置信度阈值，如果获取失败或未配置则返回None
        """
        if self._utils_service is None:
            logger.info("未配置UtilsService，跳过置信度阈值获取")
            return None
            
        try:
            threshold_score = await self._utils_service.get_threshold_score()
            # if threshold_score is not None:
            #     logger.debug(f"成功获取置信度阈值：{threshold_score}")
            # else:
            #     logger.warning("未获取到置信度阈值配置，将不进行置信度过滤")
            return threshold_score
        except Exception as e:
            logger.error(f"获取置信度阈值失败：{str(e)}，将不进行置信度过滤")
            return None
    
    def _limit_intents(self, result: Dict[str, Any], top_intents: int, confidence_threshold: Optional[float] = None) -> Dict[str, Any]:
        """
        限制返回的 Intent 数量，支持置信度阈值过滤。
        
        过滤顺序：先按置信度阈值过滤，再按数量限制。
        
        Args:
            result: 分析结果
            top_intents: 限制返回的Intent数量
            confidence_threshold: 置信度阈值，低于此值的意图会被过滤
            
        Returns:
            过滤后的结果
        """
        try:
            intents = result.get('result', {}).get('prediction', {}).get('intents', [])
            if not intents or not isinstance(intents, list):
                return result
            
            # Step 1: 先按置信度阈值过滤（如果指定了阈值）
            if confidence_threshold is not None:
                intents = [intent for intent in intents 
                          if intent.get('confidenceScore', 0.0) >= confidence_threshold]
            
            # Step 2: 按置信度排序
            sorted_intents = sorted(intents, key=lambda x: x.get('confidenceScore', 0.0), reverse=True)
            
            # Step 3: 限制数量（如果指定了数量限制）
            if top_intents and top_intents > 0:
                limited_intents = sorted_intents[:top_intents]
            else:
                limited_intents = sorted_intents
            
            # 更新结果
            prediction = result['result']['prediction']
            prediction['intents'] = limited_intents
            if limited_intents:
                prediction['topIntent'] = limited_intents[0]['category']
            else:
                # 如果过滤后没有剩余意图，设置默认值
                prediction['topIntent'] = None
            
            return result
        except Exception:
            return result  # 出错时返回原始结果
    
    def _select_best_intent(self, results: List[Dict[str, Any]], confidence_threshold: Optional[float] = None) -> Dict[str, Any]:
        """
        从批量分析结果中选择最佳意图。
        
        选择逻辑：
        1. 只考虑成功的结果
        2. 选择置信度最高的意图（包括 "None" 意图）
        3. 如果有多个相同置信度的意图，优先选择非 "None" 意图
        4. 如果最佳意图的置信度低于阈值，返回None相关信息
        
        Args:
            results: 批量分析的结果列表
            confidence_threshold: 置信度阈值，低于此值的意图会被过滤为None
            
        Returns:
            包含最佳意图信息的字典，包含以下键：
            - intent: 意图名称（如果低于阈值则为None）
            - project: 项目名称  
            - deployment: 部署名称
            - confidence: 置信度分数
            - entities: 实体列表（如果存在）
        """
        
        # 分别收集非None意图和None意图
        non_none_intents = []  # 非None意图列表
        none_intents = []      # None意图列表
        
        for result in results:
            if result.get("status") != "success":
                continue
                
            try:
                # 解析结果数据结构
                result_data = result.get("result", {})
                
                # 根据实际 JSON 结构解析数据
                # 支持两种可能的结构：直接的 prediction 或嵌套的 result.prediction
                prediction = result_data.get("prediction")
                if not prediction and "result" in result_data:
                    prediction = result_data["result"].get("prediction", {})
                
                if not prediction:
                    logger.warning(f"No prediction found in result for {result.get('projectName')}")
                    continue
                
                # 获取意图列表
                intents = prediction.get("intents", [])
                if not intents:
                    logger.warning(f"No intents found for {result.get('projectName')}")
                    continue
                
                # 获取实体信息
                entities = prediction.get("entities", [])
                
                # 遍历意图列表，分类收集
                for intent in intents:
                    if not isinstance(intent, dict):
                        continue
                        
                    category = intent.get("category", "")
                    confidence = intent.get("confidenceScore", 0.0)
                    project_name = result.get("projectName", "")
                    deployment_name = result.get("deploymentName", "")
                    is_none = category.lower() == "none"
                    
                    intent_info = {
                        "intent": category,
                        "confidence": confidence,
                        "project": project_name,
                        "deployment": deployment_name,
                        "entities": entities if entities else None,
                        "is_none": is_none
                    }
                    
                    if is_none:
                        none_intents.append(intent_info)
                    else:
                        non_none_intents.append(intent_info)
                            
            except Exception as e:
                logger.error(f"Error processing result for {result.get('projectName')}: {str(e)}")
                continue
        
        # 按置信度排序
        non_none_intents.sort(key=lambda x: x["confidence"], reverse=True)
        none_intents.sort(key=lambda x: x["confidence"], reverse=True)
        
        # 选择最佳意图：优先选择非None意图且置信度>=阈值的结果
        best_intent_info = None
        
        if non_none_intents:
            candidate = non_none_intents[0]  
            if confidence_threshold is None or candidate["confidence"] >= confidence_threshold:
                best_intent_info = candidate
                logger.info(f"Selected non-None intent: {candidate['intent']} from {candidate['project']}/{candidate['deployment']} with confidence {candidate['confidence']}")
        
        if best_intent_info is None and none_intents:
            candidate = none_intents[0]  
            if confidence_threshold is None or candidate["confidence"] >= confidence_threshold:
                best_intent_info = candidate
                logger.info(f"Selected None intent from {candidate['project']}/{candidate['deployment']} with confidence {candidate['confidence']}")
        
        if best_intent_info is None:
            all_intents = non_none_intents + none_intents
            if all_intents:
                all_intents.sort(key=lambda x: x["confidence"], reverse=True)
                candidate = all_intents[0]
                logger.info(f"No intent meets threshold {confidence_threshold}, highest confidence is {candidate['confidence']}, returning None")
                return {
                    "intent": None,
                    "project": candidate["project"],
                    "deployment": candidate["deployment"],
                    "confidence": candidate["confidence"],
                    "entities": candidate["entities"]
                }
        
        # 返回找到的最佳意图
        if best_intent_info:
            return {
                "intent": best_intent_info["intent"],
                "project": best_intent_info["project"],
                "deployment": best_intent_info["deployment"],
                "confidence": best_intent_info["confidence"],
                "entities": best_intent_info["entities"]
            }
        
        # 没有找到任何有效意图
        logger.warning("No valid intents found in any results")
        return {
            "intent": None,
            "project": None,
            "deployment": None,
            "confidence": None,
            "entities": None
        }